package xxl.robotlegs.extensions.mediatormap.impl 
{
	import flash.utils.Dictionary;
	import xxl.robotlegs.extensions.matching.api.ITypeFilter;
	import xxl.robotlegs.extensions.mediatormap.api.IMediatorMapping;
	import xxl.robotlegs.framework.api.IInjector;
	import xxl.robotlegs.utils.GuardUtil;
	import xxl.robotlegs.utils.HookUtil;
	/**
	 * ...
	 * @author XXL
	 */
	public class MediatorFactory 
	{
		private const _mediators:Dictionary = new Dictionary();
		private var _injector:IInjector;
		private var _manager:MediatorManager;
		
		public function MediatorFactory(injector:IInjector, manager:MediatorManager = null) 
		{
			_injector = injector;
			_manager = manager || new MediatorManager(this);
		}
		
		public function getMediator(item:Object, mapping:IMediatorMapping):Object
		{
			return _mediators[item] ? _mediators[item][mapping] : null;
		}
		
		public function createMediators(item:Object, type:Class, mappings:Array):Array
		{
			const createdMediators:Array = [];
			const len:int = mappings.length;
			for (var i:int = 0; i < len; i++) 
			{
				const mapping:IMediatorMapping = mappings[i];
				var mediator:Object = getMediator(item, mapping);
				if (!mediator)
				{
					mapTypeForFilterBinding(mapping.matcher, type, item);
					mediator = createMediator(item, mapping);
					unmapTypeForFilterBinding(mapping.matcher, type, item);
				}
				if (mediator)
				{
					createdMediators.push(mediator);
				}
			}
			return createdMediators;
		}
		
		public function removeMediators(item:Object):void
		{
			const mediators:Dictionary = _mediators[item];
			if (!mediators)
			{
				return ;
			}
			for (var mapping:Object in mediators)
			{
				_manager.removeMediator(mediators[mapping], item, mapping as IMediatorMapping);
			}
			delete _mediators[item];
		}
		
		public function removeAllMediators():void
		{
			for (var item:Object in _mediators)
			{
				removeMediators(item);
			}
		}
		
		//----------private
		
		private function mapTypeForFilterBinding(filter:ITypeFilter, type:Class, item:Object):void
		{
			const requiredTypes:Vector.<Class> = requiredTypesFor(filter, type);
			const len:int = requiredTypes.length;
			for (var i:int = 0; i < len; i++) 
			{
				_injector.map(requiredTypes[i]).toValue(item);
			}
		}
		
		private function unmapTypeForFilterBinding(filter:ITypeFilter, type:Class, item:Object):void
		{
			const requiredTypes:Vector.<Class> = requiredTypesFor(filter, type);
			const len:int = requiredTypes.length;
			for (var i:int = 0; i < len; i++) 
			{
				const requiredType:Class = requiredTypes[i];
				if (_injector.satisfiesDirectly(requiredType))
				{
					_injector.unmap(requiredType);
				}
			}
		}
		
		private function requiredTypesFor(filter:ITypeFilter, type:Class):Vector.<Class>
		{
			const requiredTypes:Vector.<Class> = filter.allOfTypes.concat(filter.anyOfTypes);
			if (requiredTypes.indexOf(type) < 0)
			{
				requiredTypes.push(type);
			}
			return requiredTypes;
		}
		
		private function createMediator(item:Object, mapping:IMediatorMapping):Object
		{
			var mediator:Object = getMediator(item, mapping);
			if (mediator)
			{
				return mediator;
			}
			if (mapping.guards.length == 0 || GuardUtil.GuardsApprove(mapping.guards, _injector))
			{
				const mediatorClass:Class = mapping.mediatorClass;
				mediator = _injector.instantiateUnmapped(mediatorClass);
				if (mapping.hooks.length > 0)
				{
					_injector.map(mediatorClass).toValue(mediator);
					HookUtil.ApplyHooks(mapping.hooks, _injector);
					_injector.unmap(mediatorClass);
				}
				addMediator(mediator, item, mapping);
			}
			return mediator;
		}
		
		private function addMediator(mediator:Object, item:Object, mapping:IMediatorMapping):void
		{
			_mediators[item] ||= new Dictionary();
			_mediators[item][mapping] = mediator;
			_manager.addMediator(mediator, item, mapping);
		}
	}

}